# MadSnake - A snake game
# Copyright (C) 2020-2021  zhiqiang.bzq
#
# See LICENSE. And if you have interest in this project, join us.
#
# zhiqiang.bzq
# bzq23@foxmail.com

from common import *
from color import Color
from item import *
import time
import random

class Snake(object):
  def __init__(self, name, snakeid):
    self.name_ = name
    self.last_move_ts_ = 0
    self.bufs_ = {}
    self.bufs_queue_ = []
    self.tail_ = None
    self.score_ = 0.
    self.alive_ = True
    self.last_event_time_ = 0.
    self.last_skill_time_ = 0.
    self.skill_cold_down_ = 60.
    self.buf_last_time_ = 10.
    self.snakeid_ = snakeid
    
  def OnInitGround(self, ground):
    self.body_, self.direction_, self.color_ = self.MakeInitBody(MIN_BODY_LEN, ground.Size())

  def MakeInitBody(self, init_len, groundsize):
    if self.snakeid_ == 0:
      y = int(groundsize.height / 2)
      x = int(groundsize.width / 10)
      return [ Point(i, y) for i in range(x + init_len - 1, x - 1, -1) ], Action.RIGHT, Color.BLUE
    elif self.snakeid_ == 1:
      y = int(groundsize.height / 2)
      x = int(groundsize.width * 9 / 10)
      return [ Point(i, y) for i in range(x - init_len + 1, x + 1, 1) ], Action.LEFT, Color.PURPLE

  def Move(self, event):
    if event == Action.UP and self.direction_ == Action.DOWN: return
    if event == Action.DOWN and self.direction_ == Action.UP: return
    if event == Action.LEFT and self.direction_ == Action.RIGHT: return
    if event == Action.RIGHT and self.direction_ == Action.LEFT: return
    self.direction_ = event
    movedeta = [
      (0, -1), (0, +1), (-1, 0), (+1, 0)
    ]
    self.tail_ = self.body_.pop()
    newhead = Point(self.body_[0].x, self.body_[0].y)
    newhead.x += movedeta[event][0]
    newhead.y += movedeta[event][1]
    self.body_.insert(0, newhead)
    self.last_move_ts_ = time.time()

  def GrowUp(self):
    self.body_.append(self.tail_)
    self.tail_ = None

  def AddBuf(self, buf):
    if buf in self.bufs_queue_:
      self.bufs_queue_.remove(buf)
    self.bufs_queue_.insert(0, buf)
    # we only allow buf count <= len(body) - 1(head)
    if len(self.bufs_queue_) + 1 >= len(self.body_):
      # we need to pop out the oldest buf.
      outbuf = self.bufs_queue_.pop()
      self.bufs_.pop(outbuf)
    # insert buf
    timeout = time.time() + self.buf_last_time_
    self.bufs_[buf] = timeout

  def Bufs(self):
    return self.bufs_queue_

  def LeftTimeForBuf(self, buf):
    return max(self.bufs_[buf] - time.time(), 0)

  def HasBuf(self, buf):
    return buf in self.bufs_

  def Die(self):
    self.alive_ = False

  def Alive(self):
    return self.alive_

  def AddScore(self, score):
    self.score_ += score

  def Score(self):
    return self.score_

  def SevenInch(self):
    # head + bufs + interval or 7
    return max(1 + len(self.bufs_queue_) + 2, 7)

  def BodyColor(self, index):
    if index == 0:
      return self.color_
    elif index <= len(self.bufs_queue_):
      buf = self.bufs_queue_[index - 1]
      leftime = self.bufs_[buf] - time.time()
      if leftime >= 3.0:
        return Item.ColorForType(self.bufs_queue_[index - 1])
      else:
        if random.random() < 0.5:
          return Item.ColorForType(self.bufs_queue_[index - 1])
        else:
          return Color.WHITE
    elif index == self.SevenInch():
      return Color.RandomColor()
    else:
      return self.color_

  def Body(self):
    return self.body_

  def Name(self):
    return self.name_

  def SkillColdDown(self):
    return max(0., self.last_skill_time_ + self.skill_cold_down_ - time.time())

  def OnEvent(self, event, allsnake):
    now = time.time()
    self.UpdateBufs(now) # keep bufs in time
    if event == Action.SKILL:
      # skill
      if now > self.last_skill_time_ + self.skill_cold_down_:
        self.OnSkill(allsnake)
        self.last_skill_time_ = now
    else:
      # move speed
      event_interval = 0.3
      if self.HasBuf(Item.SHOEBUF):
        event_interval = 0.
      elif self.HasBuf(Item.SLOWDEBUF):
        event_interval = 1
      if now >= self.last_event_time_ + event_interval:
        self.last_event_time_ = now
        self.Move(event)

  def OnSkill(self, allsnake):
    pass

  def OnTimer(self, automove_interval):
    now = time.time()
    self.UpdateBufs(now) # keep bufs in time
    if now - self.last_move_ts_ > automove_interval:
      self.Move(self.direction_)

  def UpdateBufs(self, now):
    for i in range(len(self.bufs_queue_) - 1, -1, -1):
      buf = self.bufs_queue_[i]
      if self.bufs_[buf] <= now:
        self.bufs_queue_.pop()
        self.bufs_.pop(buf)
      else:
        break

  def RemoveBuf(self, buf):
    if not self.HasBuf(buf): return
    self.bufs_.pop(buf)
    self.bufs_queue_.remove(buf)
  
  def Cut(self, index):
    if index >= len(self.body_): return
    # remove body.
    while len(self.body_) > index:
      self.tail_ = self.body_.pop()
    # remove bufs.
    while len(self.bufs_queue_) > index - 1:
      buf = self.bufs_queue_.pop()
      self.bufs_.pop(buf)      

  def Skill(self):
    return ""

class ThiefSnake(Snake):
  def OnSkill(self, allsnake):
    now = time.time()
    def InRange(snake):
      if snake == self: False
      head = snake.Body()[0]
      myhead = self.body_[0]
      range_radio = 5
      return (head.x - myhead.x) * (head.x - myhead.x) + (head.y - myhead.y) * (head.y - myhead.y) <= range_radio * range_radio
    # steal a random buf from every other snake in range.
    for snake in allsnake.values():
      if InRange(snake):
        if len(snake.bufs_queue_) > 0:
          buf = random.choice(snake.bufs_queue_)
          snake.RemoveBuf(buf)
          # add buf with new last time
          self.AddBuf(buf)
  def Skill(self):
    return "Steal-Buf"

class WarriorSnake(Snake):
  def OnSkill(self, allsnake):
    now = time.time()
    # double the left time of all buf.
    for buf in self.bufs_.keys():
      if Item.IsBuf(buf):
        self.bufs_[buf] = (self.bufs_[buf] - now) * 2 + now
    # sort bufs queue,
    def compare(x, y):
      return 1 if self.bufs_[x] < self.bufs_[y] else -1 if self.bufs_[x] > self.bufs_[y] else 0
    self.bufs_queue_.sort(key=compare)
  def Skill(self):
    return "Double-Buf"

class MageSnake(Snake):
  def OnSkill(self, allsnake):
    # get a random buf.
    self.AddBuf(Item.GenerateRandomBufType())
  def Skill(self):
    return "Random-Buf"

